#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

using OSH;
using Osh;
using Osh.Media;
using Osh.Scripting;

namespace OshHost
{
	public delegate void ActivateHostHandler();

	/// <summary>
	/// Summary description for Host.
	/// </summary>
	public class Host : Osh.OshHostBase, OSH.IHost, IDisposable
	{
		#region Native API
		private const int ACTIVEOBJECT_STRONG = 0;
		private const int ACTIVEOBJECT_WEAK = 1;

		[DllImport("oleaut32.dll", PreserveSig=false)]
		private static extern void GetActiveObject(ref Guid rclsid, uint reserved,
			[MarshalAs(UnmanagedType.Interface)] out object ppunk);

		[DllImport("oleaut32.dll", PreserveSig=false)]
		private static extern void RegisterActiveObject([MarshalAs(UnmanagedType.IUnknown)] object obj, 
			ref Guid rclsid, int dwFlags, out IntPtr pdwRegister);

		[DllImport("oleaut32.dll", PreserveSig=false)]
		private static extern void RevokeActiveObject(IntPtr dwRegister, uint pvReserved);
		#endregion Native API

		private Guid _iHostGuid = Guid.Empty;
		private string _oshSourceName;
		private IntPtr _objectHandle = IntPtr.Zero;
		private Hashtable _services = new Hashtable();
		private WebBrowserEx _webBrowser;
		private WebBrowserEx _traceWebBrowser;
		private OshApplication _osh;

		public Host()
		{
			_osh = new OshApplication();
			_oshSourceName = Guid.NewGuid().ToString();
			_iHostGuid = Marshal.GenerateGuidForType(typeof(OSH.IHost));
			//System.Diagnostics.Trace.Listeners.Add(new HtmlWriterTraceListener());
		}

		public WebBrowserEx WebBrowser
		{
			get { return _webBrowser; }
			set
			{
				_webBrowser = value;
				if(value != null)
					_osh.OshSource = new OshSource(_osh, value);
				else
					_osh.OshSource = null;
			}
		}

		public WebBrowserEx TraceWebBrowser
		{
			get { return _traceWebBrowser; }
			set { _traceWebBrowser = value; }
		}

		public bool Active
		{
			get { return _objectHandle != IntPtr.Zero; }
		}

		private bool IsAnotherHostRegistered
		{
			get
			{
				object otherHost = null;
				try
				{
					GetActiveObject(ref _iHostGuid, 0, out otherHost);
				}
				catch(COMException) { }
				return otherHost != null;
			}
		}

		public void Register()
		{
			if(_objectHandle != IntPtr.Zero)
				return;

			if(this.IsAnotherHostRegistered)
				throw new ApplicationException("Another OSH host is active.");

			try
			{
				RegisterActiveObject((OSH.IHost)this, ref _iHostGuid, ACTIVEOBJECT_STRONG, out _objectHandle);
			}
			catch(COMException e)
			{
				throw new ApplicationException("Can't register as OSH Host.", e);
			}
		}

		public void Unregister()
		{
			if(_objectHandle == IntPtr.Zero)
				return;

			try
			{
				RevokeActiveObject(_objectHandle, 0);
				_objectHandle = IntPtr.Zero;
			}
			catch(COMException e)
			{
				throw new ApplicationException("Can't unregister as OSH Host.", e);
			}
		}

		/// <summary>
		/// Provides access to the configuration from script.
		/// </summary>
		public OshHost.IOshHostSection HostConfiguration
		{
			get { return OshHost.Configuration.OshHost; }
		}

		public Osh.Configuration.Configuration OshConfiguration
		{
			get { return OshHost.Configuration.Global; }
		}

		#region IHost Members
		public override IOsh Osh
		{
			get { return _osh; }
		}

		public string SourceName
		{
			get { return _oshSourceName; }
		}
		
		public void Activate()
		{
			if(ActivateHost != null)
				ActivateHost();
		}
		#endregion

		public override object InvokeScript(string scriptName, params object[] args)
		{
			if(_webBrowser == null)
				throw new InvalidOperationException("OSH browser has not been initialized yet.");

			return _webBrowser.InvokeScript(scriptName, args);
		}

		public Osh.Scripting.EventSource AttachEvent(object obj, string eventName, string callback)
		{
			return new Osh.Scripting.EventSource(obj, eventName, callback);
		}
		public void DetachAllEvents()
		{
			EventSource.DetachAllEvents();
		}

		public string GetTypeFullName(object obj)
		{
			if(obj == null)
				throw new ArgumentNullException("obj");
			return obj.GetType().FullName;
		}

		public bool IsInterfaceImplementedBy(object obj, string interfaceName)
		{
			if(obj == null)
				throw new ArgumentNullException("obj");
			var interfaceType = obj.GetType().GetInterface(interfaceName, false);
			return interfaceType != null;
		}

		/// <summary>
		/// Helps script code to deal with all sort of managed collections.
		/// </summary>
		/// <param name="obj"></param>
		/// <returns></returns>
		public object ConvertToEnumerable(object obj)
		{
			if(obj == null)
				throw new ArgumentNullException("obj");

			if(obj is ICollection)
				return new ArrayList((ICollection)obj);
			if(obj is IEnumerable)
			{
				ArrayList list = new ArrayList();
				IEnumerator enumerator = (obj as IEnumerable).GetEnumerator();
				while(enumerator.MoveNext())
					list.Add(enumerator.Current);
				return list;
			}
			throw new ArgumentOutOfRangeException("obj");
		}

		public void SendOshCommand(string command)
		{
			if(string.IsNullOrEmpty(command))
				throw new ArgumentNullException("command");

			command = command.Replace("\"", "\"\"");
			string code = string.Format("window.top.OSH.getCurrent().processCommand(\"" + command + "\");");
			InvokeScript("eval", code);
		}

		public object GetFrameDocument(object frame)
		{
			return _webBrowser.GetFrameDocument(frame);
		}

		public event ActivateHostHandler ActivateHost;

		#region IDisposable Members
		~Host()
		{
			Dispose(false);
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		private bool _disposed = false;
		protected void Dispose(bool disposing)
		{
			if(!_disposed)
				return;

			_disposed = true;
			if(disposing)
			{
				// Dispose managed resources.
			}
			Unregister();
		}
		#endregion IDisposable Members

		public static new Host Current
		{
		    get
		    {
		        if(OshHostBase.Current == null)
					lock(typeof(Host))
				        if(OshHostBase.Current == null)
				            OshHostBase.Current = new Host();

				return (Host)OshHostBase.Current;
		    }
		}

	}
}
